﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>

using namespace std;
//1 引用 

//1.1引用概念
//不是新定义一个变量，而是给已存在变量取了一个别名，编译器不会为引用变量开辟内存空
//它和它引用的变量共用同一块内存空间

//在c++中，&就不是取地址了，而是引用的意思了
//&的使用：
//类型& 引用变量名(对象名) = 引用实体；

void TestRef()
{
	int a = 10;
	int& ra = a;//<====定义引用类型


	cout << &a << endl;
	cout << &ra << endl;//这里对a和ra取地址，输出结果都是同一块空间

	ra++;//如果对一个ra++,那么ra和a都会加加
}
//注意：类型必须和引用实体是同种类型的

//1.2引用的特性
//（1）引用在定义时必须初始化，也就是不能写int& a 这样的代码
//（2）一个变量可以有多个引用
//（3）引用一旦引用一个实体，再不能引用其他实体

void TestRef()
{
	int a = 10;
	// int& ra;   // 该条语句编译时会出错
	int& ra = a;
	int& rra = a;
	cout << &ra << endl;
	cout << &rra << endl;

	int x = 10;
	rra = x;//这里的rra不会被改变，因为在c++中，一个引用只能引用一个实体
	//这里是将x的值赋值给rra，但是rra依旧是a的别名，也就是a的值也同时跟着rra被改变了
}

//1.3引用的使用场景 

//（1）引用做参数 【两大用途：1.输出型参数（形参的改变要改变实参）2.提高效率（大对象/深拷贝的类对象）】
  //1.输出型参数
void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
  //2.提高效率（大对象/深拷贝的类对象）
#include <time.h>
struct A 
{ 
	int a[10000]; 
};

void TestFunc1(A a) {}//在c++中结构体不用在加上前面的struct
void TestFunc2(A& a) {}

void TestRefAndValue()
{
	A a;

	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();

	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();

	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

//（2）引用做返回值（作用1：减少拷贝，提高效率  作用2：修改返回值）
// 1）传引用返回和传值返回
int& Count1()//传引用返回
{
	static int n = 0;
	n++;
	return n;//返回的是n的别名，n的引用
}

int Count2()//传值返回
{
	int n = 0;
	n++;
	return n;
}
//在Count2中，其函数的返回会先把返回值给一个临时变量（这个临时变量是临时生成的），然后在返回这个值
//产生临时变量的原因：因为函数出了这个栈帧就被销毁了，所以必须要产生一个临时变量返回
//那么如果能够不产生临时变量，就会减少拷贝，提高效率，那么如何不产生临时变量呢？
//传引用调用就可以解决这个问题，例如Count1

// 2）传值返回和传引用返回的时间效率对比
struct A 
{ 
	int a[10000]; 
};

A a;//在c++中定义结构体不用带上前面的struct

// 值返回
A TestFunc1() 
{ 
	return a; 
}

// 引用返回
A& TestFunc2() 
{ 
	return a; 
}
void TestReturnByRefOrValue()
{

	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();

	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();

	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

// 2）传引用返回的使用条件

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);//ret是Add函数中c的别名
	cout << "Add(1, 2) is :" << ret << endl;

	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;//运行之后发现，调用了Add函数并没接收返回值，但是ret的值已经变了
	return 0;
}
//上面这个程序打印的ret的值是不确定的，因为在返回的时候，Add的栈帧已经销毁了，把操作权限还给操作系统了
//如果栈帧被清理了，那么就是随机值，但如果未被清理，就是正确的值
//所以，严格上来说，上面这个Add函数不能使用引用返回，因为出了作用域，变量的值就不在了

//综上所述：返回局部变量的引用是很危险的
//所以，如果函数返回时，出了函数作用域，如果返回对象还在(还没还给系统)，例如，静态区的变量，则可以使用
//引用返回，如果已经还给系统了，则必须使用传值返回


//总结：
//1.基本任何情况下都可以使用引用传参
//2.谨慎用引用做返回值，出了函数作用域，对象不在了，就不可以使用引用返回，对象还在，就可以使用引用返回


//1.4常引用

void TestConstRef()
{
	//1.引用的过程中，权限不能放大
	const int a = 10;
	//int& ra = a;   // 该语句编译时会出错，a为常量

	//2.引用过程中，权限可以平移或者缩小
	double d = 12;
	//int& rd = d; // 该语句编译时会出错，类型不同
	const double& rd = d;//可以
	d++;//这个操作可以，d可以进行加加，但是rd不能加加，但是d加加rd同时也加加了，只是不能通过rd来修改

	const int& ra = a;
	// int& b = 10; // 该语句编译时会出错，b为常量
	//给常量取别名应该是这样：
	const int& b = 10;

	///3.在发生整型提升或者截断等类型转换时会产生临时变量，而临时变量具有常性
	double dd = 3.14;
	int ii = dd;//可以，类型转换
	// int& rii = dd;          //不可以，权限被放大了
	const int& ii = dd;//这里进行强制类型转换时，具有常性，所以必须加上const
}

//易错：
int func1()
{
	static int a = 0;
	return a;
}
int& func2()
{
	static int a = 0;
	return a;
}
int main()
{
	//  int& ret = func1();
	// 上面这个操作是不可以的，因为返回的是临时变量，返回的时候权限被放大了
	//但加个const就对了
	const int& ret1 = func1();//权限的平移

	int& ret2 = func2();//权限的平移
	const int& ret3 = func2();//权限的缩小

	return 0;
}


//1.5引用和指针的区别 


//1)在语法概念上引用就是一个别名，没有独立空间，和其引用实体共用同一块空间

int main()
{
	int a = 10;
	//语法层面：不开空间，对a进行取别名
	int& ra = a;

	//语法层面：开空间，存储a的地址
	int* pa = &a;
	*pa = 20;

	cout << "&a = " << &a << endl;
	cout << "&ra = " << &ra << endl;
	return 0;
}

//2）在底层实现上实际是有空间的，因为引用是按照指针方式来实现的

int main()
{
	int a = 10;

	int& ra = a;
	ra = 20;

	int* pa = &a;
	*pa = 20;

	return 0;
}

//引用和指针的不同点:
//1. 引用概念上定义一个变量的别名，指针存储一个变量地址。
//2. 引用在定义时必须初始化，指针没有要求
//3. 引用在初始化时引用一个实体后，就不能再引用其他实体，而指针可以在任何时候指向任何
//一个同类型实体
//4. 没有NULL引用，但有NULL指针
//5. 在sizeof中含义不同：引用结果为引用类型的大小，但指针始终是地址空间所占字节个数(32
//位平台下占4个字节)
//6. 引用自加即引用的实体增加1，指针自加即指针向后偏移一个类型的大小

